#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <assert.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/resource.h>  /* getrlimit() */
#include <unistd.h>  /* getpagesize() */
#include <limits.h>
#include <utils.h>
#include <thread.h>
#include <printk.h>

#undef NANOSEC
#define NANOSEC ((uint64_t) 1e9)

int svc_barrier_init(svc_barrier_t *barrier, unsigned int count)
{
    return pthread_barrier_init(barrier, NULL, count);
}
int svc_barrier_wait(svc_barrier_t *barrier)
{
    int rc;

    rc = pthread_barrier_wait(barrier);
    return rc == PTHREAD_BARRIER_SERIAL_THREAD;
}
void svc_barrier_destroy(svc_barrier_t *barrier)
{
    pthread_barrier_destroy(barrier);
}


/*
 * On Linux, threads created by musl have a much smaller stack than threads
 * created by glibc (80 vs. 2048 or 4096 kB.)  Follow glibc for consistency.
 */
static size_t thread_stack_size(void)
{
    struct rlimit lim;

    getrlimit(RLIMIT_STACK, &lim);
    if (lim.rlim_cur != RLIM_INFINITY)
    {
        /* pthread_attr_setstacksize() expects page-aligned values. */
        lim.rlim_cur -= lim.rlim_cur % (rlim_t)getpagesize();
        if (lim.rlim_cur >= PTHREAD_STACK_MIN)
            return lim.rlim_cur;
    }

    return 2 << 20;  /* glibc default. */
}

int svc_thread_create_complex(svc_thread_t *tid, u32 stack_size,
                              void (*entry)(void *arg), void *arg)
{
    int r;
    svc_thread_t _tid;
    pthread_attr_t attr;

    if (!tid) tid = &_tid;

    if (stack_size < PTHREAD_STACK_MIN)
        stack_size = PTHREAD_STACK_MIN;

    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, stack_size);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    r = pthread_create(tid, &attr, (void *(*)(void *))entry, arg);
    pthread_attr_destroy(&attr);
    return r;
}
int svc_thread_create(svc_thread_t *tid, void (*entry)(void *arg), void *arg)
{
    size_t stack_size = thread_stack_size();

    return svc_thread_create_complex(tid, stack_size, entry, arg);
}

svc_thread_t svc_thread_self(void)
{
    return pthread_self();
}
int svc_thread_join(svc_thread_t *tid)
{
    return pthread_join(*tid, NULL);
}
int svc_thread_equal(const svc_thread_t *t1, const svc_thread_t *t2)
{
    return pthread_equal(*t1, *t2);
}


int svc_mutex_init(svc_mutex_t *mutex)
{
#if defined(NDEBUG) || !defined(PTHREAD_MUTEX_ERRORCHECK)
    return pthread_mutex_init(mutex, NULL);
#else
    pthread_mutexattr_t attr;
    int err;

    if (pthread_mutexattr_init(&attr))
        abort();

    if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK))
        abort();

    err = pthread_mutex_init(mutex, &attr);

    if (pthread_mutexattr_destroy(&attr))
        abort();

    return UV__ERR(err);
#endif
}
int svc_mutex_init_recursive(svc_mutex_t *mutex)
{
    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

    int err = pthread_mutex_init(mutex, &attr);

    pthread_mutexattr_destroy(&attr);
    return err;
}
void svc_mutex_destroy(svc_mutex_t *mutex)
{
    pthread_mutex_destroy(mutex);
}
void svc_mutex_lock(svc_mutex_t *mutex)
{
    pthread_mutex_lock(mutex);
}
int svc_mutex_trylock(svc_mutex_t *mutex)
{
    int err;

    err = pthread_mutex_trylock(mutex);
    if (err)
    {
        assert(err == EBUSY || err == EAGAIN);
        return -EBUSY;
    }
    return 0;
}
void svc_mutex_unlock(svc_mutex_t *mutex)
{
    pthread_mutex_unlock(mutex);
}


int svc_rwlock_init(svc_rwlock_t *rwlock)
{
    return pthread_rwlock_init(rwlock, NULL);
}
void svc_rwlock_destroy(svc_rwlock_t *rwlock)
{
    pthread_rwlock_destroy(rwlock);
}
void svc_rwlock_rdlock(svc_rwlock_t *rwlock)
{
    pthread_rwlock_rdlock(rwlock);
}
int svc_rwlock_tryrdlock(svc_rwlock_t *rwlock)
{
    int err;

    err = pthread_rwlock_tryrdlock(rwlock);
    if (err)
    {
        assert(err == EBUSY || err == EAGAIN);
        return -EBUSY;
    }
    return 0;
}
void svc_rwlock_rdunlock(svc_rwlock_t *rwlock)
{
    pthread_rwlock_unlock(rwlock);
}
void svc_rwlock_wrlock(svc_rwlock_t *rwlock)
{
    pthread_rwlock_wrlock(rwlock);
}
int svc_rwlock_trywrlock(svc_rwlock_t *rwlock)
{
    int err;

    err = pthread_rwlock_trywrlock(rwlock);
    if (err)
    {
        assert(err == EBUSY || err == EAGAIN);
        return -EBUSY;
    }
    return 0;
}
void svc_rwlock_wrunlock(svc_rwlock_t *rwlock)
{
    pthread_rwlock_unlock(rwlock);
}


void svc_once(svc_once_t *guard, void (*callback)(void))
{
    pthread_once(guard, callback);
}

int svc_sem_init(svc_sem_t *sem, unsigned int value)
{
    return sem_init(sem, 0, value);
}
void svc_sem_destroy(svc_sem_t *sem)
{
    sem_destroy(sem);
}

void svc_sem_post(svc_sem_t *sem)
{
    sem_post(sem);
}
void svc_sem_wait(svc_sem_t *sem)
{
    int r;

    do
        r = sem_wait(sem);
    while (r == -1 && errno == EINTR);
}
int svc_sem_trywait(svc_sem_t *sem)
{
    int r;

    do
        r = sem_trywait(sem);
    while (r == -1 && errno == EINTR);

    if (r)
    {
        if (errno == EAGAIN)
            return -EAGAIN;
    }
    return 0;
}


int svc_cond_init(svc_cond_t *cond)
{
    pthread_condattr_t attr;
    int err;

    err = pthread_condattr_init(&attr);
    if (err) return -err;

    err = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
    if (err) goto error2;

    err = pthread_cond_init(cond, &attr);
    if (err) goto error2;

    err = pthread_condattr_destroy(&attr);
    if (err) goto error;

    return 0;

error:
    pthread_cond_destroy(cond);
error2:
    pthread_condattr_destroy(&attr);
    return -err;
}

void svc_cond_destroy(svc_cond_t *cond)
{
    pthread_cond_destroy(cond);
}
void svc_cond_signal(svc_cond_t *cond)
{
    pthread_cond_signal(cond);
}
void svc_cond_broadcast(svc_cond_t *cond)
{
    pthread_cond_broadcast(cond);
}
void svc_cond_wait(svc_cond_t *cond, svc_mutex_t *mutex)
{
    pthread_cond_wait(cond, mutex);
}
int svc_cond_timedwait(svc_cond_t *cond, svc_mutex_t *mutex, u32 timeout)
{
    struct timespec ts;
    u64 to = hw_time() + (timeout / 10);

    ts.tv_sec  = (to / 100);
    ts.tv_nsec = (to % 100) * 1e7;
    return pthread_cond_timedwait(cond, mutex, &ts);
}


int svc_key_create(svc_key_t *key)
{
    return pthread_key_create(key, NULL);
}
void svc_key_delete(svc_key_t *key)
{
    pthread_key_delete(*key);
}
void *svc_key_get(svc_key_t *key)
{
    return pthread_getspecific(*key);
}
void svc_key_set(svc_key_t *key, void *value)
{
    pthread_setspecific(*key, value);
}

void svc_sleep(unsigned int usec)
{
    struct timespec timeout;
    int rc;

    timeout.tv_sec = usec / 1000000;
    timeout.tv_nsec = (usec % 1000000) * 1000;

    do
        rc = nanosleep(&timeout, &timeout);
    while (rc == -1 && errno == EINTR);
}

int system_run(const char *cmd, ...)
{
    char command[1024];
    va_list args;

    va_start(args, cmd);
    vsnprintf(command, sizeof(command), cmd, args);
    va_end(args);

    APP_PI("<SYSTEM>: run[%s]...\n", command);
    return system(command);
}
